/***************************************************************************
                          local_game.c  -  description
                             -------------------
    begin                : Thu Sep 6 2001
    copyright            : (C) 2001 by Michael Speck
    email                : kulkanie@gmx.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "lbreakout.h"
#include "../game/game.h"
#include "../gui/gui.h"
#include "client_data.h"
#include "event.h"
#include "config.h"
#include "shrapnells.h"
#include "player.h"
#include "display.h"
#include "paddle.h"
#include "chart.h"
#include "shine.h"
#include "credit.h"
#include "bricks.h"
#include "shots.h"
#include "frame.h"
#include "balls.h"
#include "extras.h"
#include "help.h"
#include "game.h"
#include "comm.h"
#include "slot.h"
#include "manager.h"

SDL_Surface *bkgnd = 0; /* current background picture */
extern SDL_Surface *nuke_bkgnd; /* nuke background */
SDL_Surface *offscreen = 0; /* buffer with frame, background and bricks */
extern StkFont *font; /* standard font */
extern Config config; /* lbreakout config struct */
extern int stk_quit_request; /* terminate game */
extern SDL_Surface *stk_display; 
extern SDL_Surface *brick_pic;
extern int player_count;
extern Player players[MAX_PLAYERS]; /* player infos */
#ifdef AUDIO_ENABLED
extern StkSound *wav_click;
extern StkSound *wav_damn, *wav_dammit, *wav_wontgiveup, *wav_excellent, *wav_verygood;
#endif
extern int ball_pic_x_offset;
extern int paddle_cw;
extern List *client_users;
extern ClientUser *client_user;
extern GuiWidget *list_users;
extern char pause_chatter[CHAT_LINE_COUNT][CHAT_LINE_WIDTH];
extern GuiWidget *gui_key_widget;
extern GuiWidget *gui_clicked_widget;
extern GuiWidget *gui_focused_widget;
extern GuiWidget *dlg_pauseroom, *dlg_chatroom;

extern List *levelset_names;
Player *cur_player = 0; /* current player */
Paddle *l_paddle = 0; /* locally controlled paddle */
Paddle *r_paddle = 0; /* remotely controlled paddle */
LevelSet *game_set; /* set that is played */
Game *local_game = 0; /* in a local game the remote side is faked in this
			 game context to have just one main loop for both game
			 types */
Game *game = 0; /* local/network game context */
int game_round; /* id of current round in network game */
int game_stats[2][7]; /* network game stats */
int stats_received = 0;
int game_over = 0; /* network game is over */
int allow_disintegrate = 1; /* wether Plane of Inner Stability may be entered */
Display *display_score[2]; /* either score or frags */
Display *display_player[2]; /* player name and lifes or wins */
extern int client_state;
extern int warp_blinks, warp_blink;
extern SDL_Surface *paddle_pic, *weapon_pic, *ball_pic, *extra_pic, *shot_pic;
extern StkFont *display_font;
extern int bkgnd_count;
int bkgnd_ids[MAX_LEVELS]; /* random background ids changed everytime a game is started */
int client_comm_delay; /* delay between communications */
int no_comm_since; /* time passed this last comm */
extern char client_name[16]; /* our local username */
int freakout_seed = 0; /* last used seed for freakout game */

extern int current_player;
extern Item *item_resume_0;

extern void select_chart( char *name, int update );

/*
====================================================================
Locals
====================================================================
*/


/* initiate the level of the game context by using the player's
 * snapshot. the snapshot must've been previously set either by 
 * net update or locally. 'l_pos' is either PADDLE_BOTTOM or TOP
 * indicating which paddle client controls in network game.
 */
static int init_level( Player *player, int l_pos )
{
	int length;
	char str[32];

    	/* init level by replacing with the players snapshot. this will
	 * screw the total brick count thus stats are useless for local
	 * game but they are not used anyway as the remote/local game
	 * context contains the real stats */
	if ( game->game_type == GT_LOCAL )
		game_init( local_game, &player->snapshot );
	game_init( game, &player->snapshot );
	
	/* initiate frame for game type */
	frame_init();

	/* create offscreen */
	offscreen = stk_surface_create( SDL_SWSURFACE, stk_display->w, stk_display->h );
	SDL_SetColorKey( offscreen, 0, 0 );

	/* add&create background */
	if ( game->game_type == GT_LOCAL )
		bkgnd_draw( bkgnd, bkgnd_ids[player->level_id] );
	else
		bkgnd_draw( bkgnd, -1 );
	/* add frame */
	frame_draw();
	/* add bricks */
	bricks_draw();
	/* draw lives */
	if ( game->game_type == GT_LOCAL )
		frame_draw_lives( player->lives, game->diff->max_lives );
   
	/* determine what is the local and what is the remote paddle and
	 * connect the proper players with each paddle */
	if ( game->game_type == GT_LOCAL ) {
		l_paddle = game->paddles[PADDLE_BOTTOM];
		l_paddle->player = player;
		r_paddle = 0;
	} else {
		if ( l_pos == PADDLE_BOTTOM ) {
			l_paddle = game->paddles[PADDLE_BOTTOM];
			r_paddle = game->paddles[PADDLE_TOP];
		} else {
			l_paddle = game->paddles[PADDLE_TOP];
			r_paddle = game->paddles[PADDLE_BOTTOM];
		}
		/* player 0 is always the local player */
		l_paddle->player = &players[0];
		r_paddle->player = &players[1];
		
		/* let top paddle use alternative graphics if any */
		if ( paddle_pic->w > 3*paddle_cw )
			game->paddles[PADDLE_TOP]->pic_x_offset = 3*paddle_cw;
	}
	player->paddle_id = l_pos;

	/* displays */
	if ( game->game_type == GT_LOCAL ) {
		/* we put these displays to the old positions 
		   at the top of the frame */
		sprintf( str, "%s", player->name );
		length = ( strlen( player->name ) ) * 8;
		display_player[0] = displays_add( 402, 0,
				length + 4, 16, str, player->lives, 0 );
		display_score[0] = displays_add( stk_display->w - 52 - 76, 0,
				76, 16, "", player->stats.total_score, 9 );
		display_player[0]->use_alpha = 0;
		display_score[0]->use_alpha = 0;
	} else {
		/* wins */
		sprintf( str, "%s ~x%i", 
			game->paddles[0]->player->name, 
			game->paddles[0]->player->stats.wins );
		length = strlen( str ) * 8;
		display_player[0] = displays_add( 
			BRICK_WIDTH + 20, ( MAP_HEIGHT - 1 ) * BRICK_HEIGHT + 2,
			length + 4, 16, str, 0, 0 );
		sprintf( str, "%s ~x%i", 
			game->paddles[1]->player->name, 
			game->paddles[1]->player->stats.wins );
		length = strlen( str ) * 8;
		display_player[1] = displays_add( 
			BRICK_WIDTH + 20, 2, length + 4, 16, str, 0, 0 );
		/* scores */
		display_score[0] = displays_add( 
				stk_display->w - BRICK_WIDTH - 20 - 76,
				( MAP_HEIGHT - 1 ) * BRICK_HEIGHT + 2,
				76, 16, "", game->paddles[0]->score, 9 );
		display_score[1] = displays_add( 
				stk_display->w - BRICK_WIDTH - 20 - 76, 2,
				76, 16, "", game->paddles[1]->score, 9 );
	}

	/* initiate credit */
    if ( game->game_type == GT_LOCAL && game_set )
	    credit_init( player->snapshot.name, player->snapshot.author, 
            player->level_id, game_set->count );
    else
	    credit_init( player->snapshot.name, player->snapshot.author, 
            player->level_id, 0/*don't display info*/ );

	/* show offscreen */
	stk_surface_blit( offscreen, 0,0,-1,-1, stk_display, 0,0 );

	/* no refresh rect as we want to use dim effect */
	return 1;
}

static void finalize_level( void )
{
	/* set alpha keys to OPAQUE */
	SDL_SetAlpha( paddle_pic, 0,0 );
	SDL_SetAlpha( weapon_pic, 0,0 );
	SDL_SetAlpha( extra_pic, 0,0 );
	SDL_SetAlpha( ball_pic, 0,0 );
	SDL_SetAlpha( shot_pic, 0,0 );
	SDL_SetAlpha( display_font->surface, 0,0 );

	/* reset ball graphic */
	ball_pic_x_offset = 0;
	
	/* reset shrapnells */
	shrapnells_reset();
	/* reset shine */
	shine_reset();
	/* reset explosions */
	exps_clear();
	exps_set_dark( 0 );

	/* delete offscreen */
	stk_surface_free( &offscreen );

	/* clear credits */
	credit_clear();
	
	/* clear displays */
	displays_clear();
	
	/* clear game contexts */
	if ( game->game_type == GT_LOCAL ) 
		game_finalize( local_game );
	game_finalize( game );
}

/* display formatted info + score table if multiple players */
static void display_score_table( char *format, ... )
{
	va_list args;
	int i;
	char info[256], buf[32];

	va_start( args, format );
	vsnprintf( info, 64, format, args );
	va_end( args );
	
	if ( player_count > 1 ) {
		strcat( info, "##" );
		for ( i = 0; i < player_count; i++ ) {
			/* add player and score */
			sprintf( buf, "#%12s %10i", " ", 
					players[i].stats.total_score );
			strcpy( buf + 1, players[i].name );
			buf[strlen(players[i].name)+1] = 32;
			strcat( info, buf );
		}
	}

	display_text( font, info );
	stk_display_update( STK_UPDATE_ALL );
}

/* begin frame by hiding all objects */
static void begin_frame( void )
{
	int i;
	
        displays_hide();
        frame_info_hide();
        extras_hide();
        for ( i = 0; i < game->paddle_count; i++ ) {
            paddle_hide( game->paddles[i] );
	    paddle_ammo_hide( game->paddles[i] );
	}
        balls_hide();
        shots_hide();
        shrapnells_hide();
        walls_hide();
        frame_warp_icon_hide();
        shine_hide();
        exps_hide();
        credit_hide();
}

/* end frame by drawing all objects and updating the screen */
static void end_frame( void )
{
	int i;
	
	/* show -- some things will be missing if darkness is enabled */
	balls_show_shadow();
	extras_show_shadow();
	for ( i = 0; i < game->paddle_count; i++ )
		paddle_show_shadow( game->paddles[i] );
	shots_show();
	if ( config.debris_level == DEBRIS_BELOW_BALL ) {
		exps_show();
		if ( !game->extra_active[EX_DARKNESS] ) {
			shrapnells_show();
			frame_info_show();
		}
	}	
	if ( config.ball_level == BALL_ABOVE_BONUS )
		extras_show();
	balls_show();
	if ( config.ball_level == BALL_BELOW_BONUS )
		extras_show();
	for ( i = 0; i < game->paddle_count; i++ )
		paddle_show( game->paddles[i] );
	if ( !game->extra_active[EX_DARKNESS] ) walls_show();
	shine_show();
	if ( config.debris_level == DEBRIS_ABOVE_BALL ) {
		exps_show();
		if ( !game->extra_active[EX_DARKNESS] ) {
			shrapnells_show();
			frame_info_show();
		}
	}	
	frame_warp_icon_show();
	displays_show();
	for ( i = 0; i < game->paddle_count; i++ )
		paddle_ammo_show( game->paddles[i] );
	credit_show();
}

/* grab/ungrab input of actual game */
static void grab_input( int grab )
{
	if ( grab ) {
		SDL_ShowCursor(0);
		SDL_WM_GrabInput( SDL_GRAB_ON );
		SDL_GetRelativeMouseState(0,0);
	} else {
		SDL_ShowCursor(1);
		SDL_WM_GrabInput( SDL_GRAB_OFF );
	}
}

/* switch client to new state */
void set_state( int newstate )
{
	if ( client_state == newstate ) return;
	
	if ( newstate == CS_PLAY )
		grab_input( 1 );
	if ( client_state == CS_PLAY )
		grab_input( 0 );
	if ( client_state == CS_CONFIRM_WARP ||
	     client_state == CS_CONFIRM_RESTART ||
	     client_state == CS_CONFIRM_QUIT ||
             client_state == CS_CONFIRM_CONTINUE ||
	     client_state == CS_GET_READY ||
	     client_state == CS_PAUSE ||
	     client_state == CS_FINAL_PLAYER_INFO ||
	     client_state == CS_RECV_LEVEL ||
	     client_state == CS_ROUND_RESULT ||
	     client_state == CS_RECV_STATS ||
	     client_state == CS_FATAL_ERROR ) {
		/* show offscreen */
		if ( offscreen ) {
			stk_surface_blit( offscreen, 0,0,-1,-1, stk_display, 0,0 );
			end_frame();
		}
		/* do not refresh when coming from RECV_LEVEL as a GET_READY
		 * will follow */
		if ( client_state != CS_RECV_LEVEL )
		if ( client_state != CS_ROUND_RESULT )
		if ( client_state != CS_RECV_STATS );
			stk_display_update( STK_UPDATE_ALL );
	}

	client_state = newstate;
	stk_timer_reset();
}

/* Fade all animations until they disappear */
static void fade_anims()
{
	float alpha = 255.0;
	int ms, i;
	stk_timer_reset();
	if ( game->game_type == GT_LOCAL && game->winner != PADDLE_BOTTOM )
		frame_remove_life();
	while ( alpha > 0 ) {
		displays_hide();
		for ( i = 0; i < game->paddle_count; i++ )
			paddle_hide( game->paddles[i] );
		balls_hide();
		extras_hide();
		shrapnells_hide();
		shots_hide();
		walls_hide();
		credit_hide();
		ms = stk_timer_get_time();
		alpha -= 0.3 * ms;
		if ( alpha < 0 ) alpha = 0;
		shrapnells_update( ms );
		for ( i = 0; i < game->paddle_count; i++ )
			paddle_alphashow( game->paddles[i], alpha );
		balls_alphashow( alpha );
		extras_alphashow( alpha );
		shots_alphashow( alpha );
		shrapnells_show();
		walls_alphashow( alpha );
		displays_show();
		credit_alphashow( alpha );
		stk_display_update( STK_UPDATE_RECTS );
	}
}

void open_pause_chat( char *text )
{
	set_state( CS_PAUSE );
	
	/* clear pause_chatter */
	memset( pause_chatter, 0, sizeof( pause_chatter ) );
	/* clear global gui widgets */
	gui_focused_widget = 0;
	gui_clicked_widget = 0;
	gui_key_widget = 0;
	/* use 'text' as initial chatter */
	client_add_pausechatter( text, 1 );
	/* gray screen */
	stk_surface_gray( stk_display, 0,0,-1,-1, 1 );
	/* show pauseroom */
	gui_widget_show( dlg_pauseroom );
	stk_display_update( STK_UPDATE_ALL );

	/* disable event filter */
	SDL_SetEventFilter( 0 );
	/* disable client_recv which is called as time event */
	gui_widget_disable_event( dlg_chatroom, GUI_TIME_PASSED );
}

void close_pause_chat( void )
{
	gui_widget_hide( dlg_pauseroom );
	set_state( CS_PLAY );

	/* enable event filter */
	SDL_SetEventFilter( event_filter );
	gui_widget_enable_event( dlg_chatroom, GUI_TIME_PASSED );
}

/* modify the client and its state according to the key pressed */
static int handle_default_key( int key, int *abort )
{
	SDL_Surface *buffer;
	
	switch ( key ) {
		case SDLK_F1:
		case SDLK_h:
			if ( client_state != CS_PLAY ) break;
			if ( game->game_type == GT_NETWORK ) break; /* only for single player */
			grab_input(0);
			help_run();
			grab_input(1);
			return 1;
		case SDLK_q:
		case SDLK_ESCAPE:
			/* recv_stats or final_stats means we already broke up
			 * the game so ESC will directly quit */
			if ( client_state == CS_RECV_STATS || client_state == CS_FINAL_STATS ) {
				*abort = 1;
				break;
			}

			if ( client_state == CS_CONFIRM_QUIT ) break;
			if ( client_state == CS_PAUSE ) break;
                        if ( players_count() == 0 ) break; 
			set_state(CS_CONFIRM_QUIT); 
                        if ( game->game_type == GT_LOCAL && game_set != 0 /*not testing a level*/ )
  			    display_text( font, "Quit Game? y/n#(If yes, this game may be resumed later.#No highscore entry is created yet.)" );
                        else
  			    display_text( font, "Quit Game? y/n" );
			return 1;
		case SDLK_r:
			if ( client_state != CS_PLAY ) break;
			if ( game->game_type == GT_NETWORK ) break; /* only for single player */
			if ( game_set == 0 ) break; /* test level */
			if ( cur_player->lives < 2 ) break;
			set_state(CS_CONFIRM_RESTART); 
			display_text( font, "Restart Level? y/n" );
			return 1;
		case SDLK_d:
			if ( client_state != CS_PLAY ) break;
			if ( game->game_type == GT_NETWORK ) break; /* only for single player */
			if ( !allow_disintegrate ) break;
			grab_input(0);
			game_nuke();
			grab_input(1);
			return 1;
		case SDLK_f:
			buffer = stk_surface_create( SDL_SWSURFACE, 640, 480 );
			SDL_BlitSurface( stk_display, 0, buffer, 0 );
			config.fullscreen = !config.fullscreen;
			stk_display_apply_fullscreen( config.fullscreen );
			SDL_BlitSurface( buffer, 0, stk_display, 0 );
			stk_display_update( STK_UPDATE_ALL);
			SDL_FreeSurface( buffer );
			return 1;
		case SDLK_s:
#ifdef AUDIO_ENABLED
			config.sound = !config.sound;
			stk_audio_enable_sound( config.sound );
#endif
			return 1;
		case SDLK_a:
			config.anim++;
			if ( config.anim >= 4 ) config.anim = 0;
			return 1;
		case SDLK_TAB:
			stk_display_take_screenshot();
			return 1;
		case SDLK_t:
			return 0;
		case SDLK_p:
			if ( client_state != CS_PLAY && client_state != CS_PAUSE ) break;
			if ( game_set == 0 ) break; /* test level */
			if ( client_state == CS_PLAY ) {
				/* pause */

				/* in local game simply darken the screen, in
				 * network game enter the pausechatroom */
				if ( game->game_type == GT_LOCAL ) {
					set_state(CS_PAUSE);
					display_text( font, "Pause" );
				}
				else {
					open_pause_chat( "You have paused the game." );
					comm_send_short( MSG_PAUSE );
				}
			} else {
				/* unpause (local game only)*/
				if ( game->game_type == GT_LOCAL )
					set_state(CS_PLAY);
			}
			return 1;
		default: 
			if ( client_state != CS_PLAY ) break;
			if ( game->game_type != GT_LOCAL ) break;
			if ( game->bricks_left > game->warp_limit ) break;
			if ( game_set == 0 ) break; /* test level */
			if ( key == config.k_warp ) {
				set_state(CS_CONFIRM_WARP);
				display_text( font, "Warp to next level? y/n" );
				return 1;
			}
			break;
	}
	
	return 0;
}

/* update local objects (shrapnells,extras,explosions...) and communicate
 * every client_comm_delay seconds either with real or fake server */
static void update_game( int ms )
{
	int i;
	
	/* run the fake server game */
	if ( game->game_type == GT_LOCAL ) {
		game_set_current( local_game );
		game_update( ms );
		game_set_current( game );
	}
		
	/* local animations and movements */
	for ( i = 0; i < game->paddle_count; i++ )
		client_paddle_update( game->paddles[i], ms );
	client_shots_update( ms );
	client_balls_update( ms );
	client_extras_update( ms );
	client_walls_update( ms );
	shrapnells_update( ms );
	frame_warp_icon_update( ms );
	shine_update( ms );
	exps_update( ms );
	displays_update( ms );
	credit_update( ms );

	/* communicate */
	if ( (no_comm_since+=ms) >= client_comm_delay ) {
		no_comm_since -= client_comm_delay;

		/* send paddle state */
		comm_send_paddle( l_paddle );
	
		/* receive game data from local or remote server and 
		 * apply it to the game context. */
		comm_recv();
		
		/* update score displays */
		display_set_value( 
			display_score[0], 
			game->paddles[0]->player->stats.total_score + 
			game->paddles[0]->score );
		if ( game->game_type == GT_NETWORK )
			display_set_value( 
				display_score[1], 
				game->paddles[1]->player->stats.total_score + 
				game->paddles[1]->score );
	}
}

/* give us a damn or excellent depending on the outcome of the level.
 * the result for network game must've been received already so that
 * game::level_over and game::winner are valid entries. */
static void play_speech( void )
{
#ifdef AUDIO_ENABLED
	if ( !config.speech ) return;
	if ( game->winner == -1 ) return; /* draw */
	
	if ( game->paddles[game->winner] == l_paddle ) {
		if ( rand() % 2 )
			stk_sound_play( wav_excellent );
		else
			stk_sound_play( wav_verygood );
	} else {
		if ( !game->diff->allow_maluses ) return; /* bad speech is bad somehow */

		if ( rand() % 2 )
			stk_sound_play( wav_damn );
		else
			stk_sound_play( wav_dammit );
	}
#endif
}

/* check players of local game wether they entered a highscore */
static void check_highscores( void )
{
	int i;
	
	chart_clear_new_entries();
	for ( i = 0; i < config.player_count; i++ )
		chart_add( 
			chart_set_query(game_set->name), 
			players[i].name, 
			players[i].level_id + 1, 
			players[i].stats.total_score );
	chart_save();
}

/* init next network game round by displaying a message and switching
 * to GET_READY. */
void init_next_round( void )
{
	game_round++;
	set_state( CS_GET_READY );
	init_level( cur_player, cur_player->paddle_id );
	display_text( font,
		"***** Round %i *****###You control the %s paddle in this level!#"
		"To fire a ball keep the mouse button PRESSED.#Don't just click.###"
		"Press any key when you are ready...###(You can pause the game with 'p' any time.)"
		"###NOTE: Due to latency, bonuses on the server are closer than they "
		"appear! I'll try to work on that.", 
		game_round, cur_player->paddle_id==0?"BOTTOM":"TOP" );
}

/* display a message about the winner */
void finalize_round( void )
{
	if ( (char)game->winner == -1 )
		display_text( font, "DRAW" );
	else {
		game->paddles[game->winner]->player->stats.wins++;
		if ( game->winner == cur_player->paddle_id )
			display_text( font, "You have won this round!" );
		else
			display_text( font, "You have lost this round." );
	}
	finalize_level();
	set_state( CS_ROUND_RESULT );
}

/* display the final statistics. the first player is always this client
 * and the second is the remote. */
void display_final_stats( void )
{
	int win;

	/* won this match? */
	if ( game_stats[0][0] > game_stats[1][0] )
		win = 1;
	else
	if ( game_stats[0][0] == game_stats[1][0] )
		win = -1;
	else
		win = 0;
	
	/* build stats string */
	display_text( font, 
			"             Result: %s              ##" \
			"                  %12s %12s##" \
			"Wins:             %12i %12i#" \
			"Losses:           %12i %12i#" \
			"Draws:            %12i %12i#" \
			"#" \
			"Total Score:      %12i %12i#" \
			"#" \
			"Balls Kept:       %11i%% %11i%%#" \
			"Bricks Cleared:   %11i%% %11i%%#" \
			"Extras Collected: %11i%% %11i%%##" \
                        "(Press SPACE to continue)",
			win==1?"VICTORY":win==0?" DEFEAT":"   DRAW",
			players[0].name, players[1].name,
			game_stats[0][0], game_stats[1][0],
			game_stats[0][1], game_stats[1][1],
			game_stats[0][2], game_stats[1][2],
			game_stats[0][3], game_stats[1][3],
			game_stats[0][4], game_stats[1][4],
			game_stats[0][5], game_stats[1][5],
			game_stats[0][6], game_stats[1][6] );
}

/* save data from local and local_game variables and update
 * the menu hint. */
void save_local_game( int slot_id )
{
    GameSlot gs;
    int i;

    memset( &gs, 0, sizeof(GameSlot) );
    strcpy( gs.setname, game_set->name );
    gs.diff = config.diff;
    gs.player_count = config.player_count;
    gs.cur_player = current_player;
    for ( i = 0; i < MAX_PLAYERS; i++ )
    {
        strcpy( gs.player_names[i], config.player_names[i] );
        gs.player_cur_level_id[i] = players[i].level_id;
        gs.player_lives[i] = players[i].lives;
        gs.player_scores[i] =  players[i].stats.total_score;
    }
    gs.freakout_seed = freakout_seed;
    if ( !slot_save( slot_id, &gs ) )
        fprintf( stderr, "ERROR: couldn't save game!\n" );
    slot_update_hint( slot_id, item_resume_0->hint );
}

/*
====================================================================
Publics
====================================================================
*/

/* create various resources like shrapnells */
void client_game_create()
{
	frame_create();
	shrapnells_init();
	shine_load();
	init_angles();

	/* background */
	bkgnd = stk_surface_create( SDL_SWSURFACE, 
			stk_display->w, stk_display->h );
	SDL_SetColorKey( bkgnd, 0, 0 );
	stk_surface_fill( bkgnd, 0,0,-1,-1, 0x0 );

}
void client_game_delete()
{
	stk_surface_free( &bkgnd );
	
	displays_clear();
	frame_delete();
	shrapnells_delete();
	shine_delete();
}

/* create network/local game context and initiate game state:
 * network needs to receive the level data and a local game
 * has to load the next level */
int client_game_init_local( char *setname )
{
	int i, warp_limit;

	warp_limit = config.rel_warp_limit;
	allow_disintegrate = 1;

	/* the original levelsets do not need these workarounds */
	if ( STRCMP( setname, "LBreakout2" ) || STRCMP( setname, "LBreakout1" ) ) {
		warp_limit = 100;
		allow_disintegrate = 0;
	}
	
	/* the approach for a local game is to use the same
	 * settings as a network game. the receiving of packets
	 * is simply faked by a local_game context that
	 * runs the game locally. but to use only one game loop
	 * we do not use it directly but apply its modificiations
	 * to game which is visualized */
	local_game = game_create( GT_LOCAL, config.diff, warp_limit );
	game_set_current( local_game );
	game_set_convex_paddle( config.convex );
	game_set_ball_auto_return( !config.return_on_click );
	game_set_ball_random_angle( config.random_angle );
        game_set_ball_accelerated_speed( config.maxballspeed_float );
	
	/* load levels:
	 * only required for local games. in network both players
	 * just require a single level that can store the incoming
	 * data that is send by the server via the net.
	 */
	if ( !strcmp( setname, TOURNAMENT ) )
	    game_set = levelset_load_all( levelset_names, freakout_seed );
	else
		game_set = levelset_load( setname );
	if ( game_set == 0 ) return 0;
	
	/* create client game context */
	game = game_create( GT_LOCAL, config.diff, warp_limit );
	game_set_current( game );
	
	/* a local game is not limited in its communication */
	client_comm_delay = 0;
	no_comm_since = 0;
	
	/* prepare warp icon at frame */
	warp_blinks = 4; warp_blink = 1;
	
	/* set list of level background ids */
	for ( i = 0; i < MAX_LEVELS; i++ )
		bkgnd_ids[i] = rand() % bkgnd_count;
	
	/* initiate players */
	players_clear();
	for ( i = 0; i < config.player_count; i++ )
		player_add( config.player_names[i], 
			    game->diff->lives, 
			    levelset_get_first( game_set ) );
	cur_player = players_get_first();

	/* init first level */
	init_level( cur_player, PADDLE_BOTTOM );
	
	/* if only one player don't show score table */
	client_state = CS_NONE;
	if ( player_count > 1 )
		set_state( CS_SCORE_TABLE );
	else
		set_state( CS_PLAY ); /* one player starts immediately */
	return 1;
}
int client_game_init_network( char *opponent_name, int diff )
{
	/* create an empty one level levelset. the server will send
	 * the data into the level everytime we play. */
	game_set = levelset_create_empty( 1, "empty", "empty" );
	
	/* create client game context */
	game = game_create( GT_NETWORK, diff, 100 );
	game_set_current( game );
	game_round = 0; /* will be increased by init_next_round() */
	game_over = 0;
	
	/* a network game communicates every 25 ms by default */
	client_comm_delay = 25;
	no_comm_since = 0;
	
	/* initiate players */
	players_clear();
	player_add( client_name, game->diff->lives, levelset_get_first( game_set ) );
	player_add( opponent_name, game->diff->lives, levelset_get_first( game_set ) );
	cur_player = players_get_first();

	display_text( font, "Receiving level data..." );
	set_state( CS_RECV_LEVEL );
	return 1;
}

/* create local game context and initiate game state
 * as given from slot 'slot_id'. */
int client_game_resume_local( int slot_id )
{
    int i;
    GameSlot gs;
   
    /* load saved game */
    if ( !slot_load( slot_id, &gs ) ) return 0;
    
    /* FIXME: config settings are overwritten for this */
    config.diff = gs.diff;
    config.player_count = gs.player_count;
    for ( i = 0; i < config.player_count; i++ )
        strcpy( config.player_names[i], gs.player_names[i] );
    freakout_seed = gs.freakout_seed;

    /* create local game where all players have full lives */
    if ( !client_game_init_local( gs.setname ) ) return 0;

    /* re-initiate players */
    players_clear();
    for ( i = 0; i < config.player_count; i++ )
    {
        /* name + lives */
        player_add( config.player_names[i], 
                gs.player_lives[i], 
                levelset_get_first( game_set ) );
        /* level */
        player_init_level( &players[i], 
                           game_set->levels[gs.player_cur_level_id[i]],
                           gs.player_cur_level_id[i] );
        /* score */
        players[i].stats.total_score = gs.player_scores[i];
    }
    cur_player = players_set_current( gs.cur_player );
    
    /* init first level */
    init_level( cur_player, PADDLE_BOTTOM );
	
    return 1;
}

/* create a one level game context for testing a level */
int client_game_init_testing( Level *level )
{
	local_game = game_create( GT_LOCAL, config.diff, 100 );
	game_set_current( local_game );
	game_set_convex_paddle( config.convex );
	game_set_ball_auto_return( !config.return_on_click );
	game_set_ball_random_angle( config.random_angle );
	
	game = game_create( GT_LOCAL, config.diff, 100 );
	game_set_current( game );

	players_clear();
	player_add( config.player_names[0], game->diff->lives, level );
	cur_player = players_get_first();

	bkgnd_ids[0] = 0;

	init_level( cur_player, PADDLE_BOTTOM );
	
	client_state = CS_NONE;
	set_state( CS_PLAY ); 

	return 1;
}

/* finalize a game and free anything allocated by init process */
void client_game_finalize()
{
	players_clear();

	if ( game && game->game_type == GT_LOCAL ) {
		game_delete( &local_game );
		levelset_delete( &game_set );
	}
	game_delete( &game );
}

/* run the state driven loop until game is broken up or finished */
void client_game_run( void )
{
	int ms, frame_delay = config.fps?10:1;
	int button_clicked, key_pressed;
	SDL_Event event;
	int abort = 0, i, j, penalty;
	/* frame rate */
	int frames = 0;
	int frame_time = SDL_GetTicks();

	event_clear_sdl_queue();
	
	stk_display_fade( STK_FADE_IN, STK_FADE_DEFAULT_TIME );
	
	stats_received = 0;
	stk_timer_reset(); ms = 1;
	while ( !abort && !stk_quit_request ) {
		/* check wether an event occured */
		button_clicked = key_pressed = 0;
		if ( SDL_PollEvent( &event ) ) {
			if ( client_state == CS_PAUSE && game->game_type == GT_NETWORK )
				gui_dispatch_event( &event, ms );
			else
			if ( event.type == SDL_MOUSEBUTTONDOWN )
				button_clicked = event.button.button;
			else
			if ( event.type == SDL_KEYDOWN ) {
				key_pressed = event.key.keysym.sym;
				if ( handle_default_key( key_pressed, &abort ) )
					key_pressed = 0;
			}
		}
		else if ( client_state == CS_PAUSE && game->game_type == GT_NETWORK )
			gui_dispatch_event( 0, ms );

		/* let server know we're still alive except
		 * in CS_PLAY as we send paddle updates there */
		if ( game->game_type == GT_NETWORK )
			comm_send_heartbeat();

		/* handle client */
		switch ( client_state ) {

		case CS_FINAL_STATS:
			if ( key_pressed==SDLK_SPACE ) abort = 1;
			break;
			
		case CS_FATAL_ERROR:
			/* after game was violently broken up the server
			 * may still send the stats of the game so far */
			if ( button_clicked || key_pressed ) {
				SDL_Delay(250); /* give time to release button */
				set_state( CS_RECV_STATS );
				display_text( font, "Receiving final stats..." );
			}
			break;
			
		case CS_FINAL_TABLE:
			if ( button_clicked || key_pressed ) {
				check_highscores();
				select_chart( game_set->name, 0 );
                                /* remove saved game */
                                slot_delete( 0 );
                                slot_update_hint( 0, item_resume_0->hint );
				/* quit local game */
				abort = 1;
			}
			break;

		case CS_SCORE_TABLE:
			/* show who's next player and scores in local game */
			display_score_table( "Next Player: %s", cur_player->name );
			set_state( CS_GET_READY );
			break;
			
		case CS_FINAL_PLAYER_INFO:
			if ( button_clicked || key_pressed ) {
				SDL_Delay(250); /* give time to release button */
				set_state( CS_NEXT_PLAYER );
			}
			break;

		case CS_RECV_LEVEL:
			comm_recv();
			if ( cur_player->next_level_received ) {
				cur_player->next_level_received = 0;
				cur_player->paddle_id = cur_player->next_paddle_id;
				init_next_round();
			}
			break;

		case CS_RECV_STATS:
			comm_recv();
			if ( stats_received ) {
				set_state( CS_FINAL_STATS );
				display_final_stats();
			}
			break;
			
		case CS_ROUND_RESULT:
			if ( button_clicked || key_pressed ) {
				SDL_Delay(250); /* give time to release button */
				if ( game_over ) {
					set_state( CS_RECV_STATS );
					display_text( font, "Receiving final stats..." );
				} else {
					set_state( CS_RECV_LEVEL );
					display_text( font, "Receiving level data..." );
 				}
			}
			break;
			
		case CS_GET_READY:
			if ( button_clicked || key_pressed ) {
				SDL_Delay(250); /* give time to release button */
				comm_send_short( MSG_READY );
				set_state( CS_PLAY );
			}
			break;

		case CS_PAUSE:
			if ( game->game_type == GT_LOCAL ) break;

			/* check wether pause chatroom has been closed
			 * either by client or remote */
			comm_recv();
			break;
			
		case CS_PLAY:
			/* hide objects */
			begin_frame();
			
			/* apply events to local paddle */
			paddle_handle_events( l_paddle, ms );

			/* update local objects and communicate if
			 * comm_delay ms have passed */
			update_game( ms );
			
			/* show objects */
			end_frame();

			/* handle local level over */
			if ( game->level_over ) {
				if ( game->game_type == GT_LOCAL ) {
					if ( game_set == 0 ) {
						abort = 1; /* was a test level */
						grab_input(0);
						break;
					}
					if ( game->winner == PADDLE_BOTTOM )
						set_state( CS_NEXT_LEVEL );
					else
						set_state( CS_LOOSE_LIFE );
				} else {
					finalize_round();
				}
			}
			break;

		case CS_NEXT_LEVEL:
			/* apply paddle stats to player */
			game_set_current( local_game );
			game_update_stats( PADDLE_BOTTOM, &cur_player->stats );
			game_set_current( game );
			/* init next level for player in local game */
			cur_player->level_id++;
			if ( cur_player->level_id >= game_set->count ) {
				/* deactivate player */
				cur_player->lives = 0;
				display_text( font, 
					"You've cleared all levels...#Congratulations!!!" );
				set_state( CS_FINAL_PLAYER_INFO );
				break;
			}
			/* get snapshot for next init */
			cur_player->snapshot = *game_set->levels[cur_player->level_id];
			/* cycle players */
			set_state( CS_NEXT_PLAYER );
			break;

		case CS_RESTART_LEVEL:
			/* apply paddle stats to player */
			game_set_current( local_game );
			game_update_stats( PADDLE_BOTTOM, &cur_player->stats );
			game_set_current( game );
			/* reset level for next turn */
			cur_player->snapshot = *game_set->levels[cur_player->level_id];
			/* decrease lives (is checked that this wasn't the last one) */
			cur_player->lives--;
			/* cycle players */
			set_state( CS_NEXT_PLAYER );
			break;
			
		case CS_LOOSE_LIFE:
			/* apply paddle stats to player */
			game_set_current( local_game );
			game_update_stats( PADDLE_BOTTOM, &cur_player->stats );
			game_set_current( game );

			/* remember level for next turn */
			game_get_level_snapshot( &cur_player->snapshot );

			/* decrease lives */
			cur_player->lives--;
			if ( cur_player->lives == 0 ) {
				display_text( font, 
					"You've lost all lives...#Do you want to buy a continue#for 100%% of your score? y/n" );
                                set_state( CS_CONFIRM_CONTINUE );
				//set_state( CS_FINAL_PLAYER_INFO );
				break;
			}
			set_state( CS_NEXT_PLAYER );
			break;

		case CS_NEXT_PLAYER:
			/* game over? */
			if ( players_count() == 0 ) {
				display_score_table( "Game Over!" );
				set_state( CS_FINAL_TABLE );
				break;
			}
			/* speak and fade */
			play_speech();
			fade_anims();
			/* finalize current game context */
			finalize_level();
			/* set next player */
			cur_player = players_get_next();
			init_level( cur_player, PADDLE_BOTTOM );
			if ( player_count > 1 )
				set_state( CS_SCORE_TABLE );
			else {
				set_state( CS_PLAY ); /* one player starts immediately */
				stk_display_update( STK_UPDATE_ALL );
			}
			break;
		
                case CS_CONFIRM_CONTINUE:
		case CS_CONFIRM_QUIT:
		case CS_CONFIRM_WARP:
		case CS_CONFIRM_RESTART:
			if ( key_pressed == 0 ) break;
			if ( key_pressed==SDLK_n||key_pressed==SDLK_ESCAPE ) {
                            /* if denying continue... DIE!!! */
                            if ( client_state == CS_CONFIRM_CONTINUE )
                            {
				SDL_Delay(250); /* give time to release button */
				set_state( CS_NEXT_PLAYER );
                                //set_state( CS_FINAL_PLAYER_INFO );
                            }
                            else
				set_state( CS_PLAY );
			    break;
			}
			if ( key_pressed != SDLK_y && key_pressed != SDLK_z ) break;
			/* handle confirmed action */
			SDL_Delay(250); /* give time to release button */
			switch( client_state ) {
                                case CS_CONFIRM_CONTINUE:
                                    /* clear score and give full lives again */
                                    cur_player->lives = game->diff->lives;
                                    cur_player->stats.total_score = 0;
                                    set_state( CS_NEXT_PLAYER );
                                    break;
				case CS_CONFIRM_QUIT:
					comm_send_short( MSG_QUIT_GAME );
					if ( game->game_type == GT_LOCAL ) {
						/* apply paddle stats to player */
						game_set_current( local_game );
						game_update_stats( PADDLE_BOTTOM, &cur_player->stats );
						game_set_current( game );
                                                /* no higscore check anymore as game is supposed to
                                                 * be resumed until normal game over */
						/* testing levels don't got for
						 * high scores ***
						if ( game_set ) {
							check_highscores();
							select_chart( game_set->name, 0 );
						}*/
                                                /* save local game */
                                                if ( game_set != 0 /*not testing a level*/ )
                                                    save_local_game( 0 );
						
                                                abort = 1;
					}
					else {
						/* await game stats */
						set_state( CS_RECV_STATS );
						display_text( font, "Receiving final stats..." );
					}
					break;
				case CS_CONFIRM_WARP:
					game->winner = -1; /* no speech */
					local_game->winner = -1; /* not counted as win */
                                        /* substract doubled score of remaining bricks */
                                        penalty = 0;
                                        for ( i = 0; i < MAP_WIDTH; i++ )
                                            for ( j = 0; j < MAP_HEIGHT; j++ )
                                                if ( local_game->bricks[i][j].dur != -1 )
                                                    penalty += local_game->bricks[i][j].score;
                                        printf( "warp penalty: -%d\n", penalty );
                                        local_game->paddles[0]->score -= penalty;
					set_state( CS_NEXT_LEVEL );
					break;
				case CS_CONFIRM_RESTART:
					game->winner = -1; /* no speech */
					local_game->winner = -1; /* not counted as win */
					local_game->level_over = 1;
					set_state( CS_RESTART_LEVEL );
					break;
			}
			break;

		}

		/* update anything that was changed */
		stk_display_update( STK_UPDATE_RECTS );

		/* get time since last call and delay if below frame_delay */
		ms = stk_timer_get_time();
		if ( ms < frame_delay ) {
			SDL_Delay( frame_delay - ms );
			ms += stk_timer_get_time();
		}
		frames++;
	}
	finalize_level();
	client_state = CLIENT_NONE;

	stk_display_fade( STK_FADE_OUT, STK_FADE_DEFAULT_TIME );
	if ( stk_quit_request )
		comm_send_short( MSG_DISCONNECT );
	else
		comm_send_short( MSG_UNHIDE );

	/* frame rate */
	frame_time = SDL_GetTicks() - frame_time;
	printf( "Time: %.2f, Frames: %i -> FPS: %.2f\n", 
		(double)frame_time / 1000, frames, 1000.0*frames/frame_time );

	event_clear_sdl_queue();

	/* update the selected user and the user list in network as 
	 * we received ADD/REMOVE_USER messages */
	gui_list_update( list_users, client_users->count );
	/* re-select current entry */
	if ( client_user ) {
		i = list_check( client_users, client_user );
		if ( i != -1 )
			gui_list_select( list_users, 0, i, 1 );
	}
}

/* test a level until all balls got lost */
void client_game_test_level( Level *level )
{
	stk_display_fade( STK_FADE_IN, STK_FADE_DEFAULT_TIME );
	client_game_init_testing( level );
	client_game_run();
	client_game_finalize();
	stk_display_fade( STK_FADE_OUT, STK_FADE_DEFAULT_TIME );
}


